我們現在已經有能力從一張圖片中,提取出一組代表其結構的「重點座標」。但電腦要如何確定這是「同一個角」,而不是兩個不同的角呢?
描述子 (descriptor) 是我們所說的「數位指紋」。它是一個由數值構成的向量,用來描述特徵點周遭的像素資訊。一個好的描述子,必須對真實世界的各種干擾具有「不變性」(invariance),例如:
尺度不變性 (scale invariance):無論物體在照片中是大是小,都應該能產生相似的描述子。
旋轉不變性 (rotation invariance):無論物體如何旋轉,描述子都應該保持一致。
光照不變性 (illumination invariance):不會因為光線變亮或變暗而產生巨大變化。
尺度不變特徵轉換 (Scale-Invariant Feature Transform, SIFT) 是電腦視覺史上最具里程碑意義的演算法之一,由 David Lowe 在1999年提出。它不僅僅是一個描述子,而是一整套包含「特徵點偵測」和「特徵點描述」的完整流程。
尺度空間極值偵測:SIFT 的第一步,就是先使用 DoG 來找到在不同尺度下都穩定的特徵點。這一步就保證了其「尺度不變性」。
關鍵點定位: 對找到的候選點進行精確定位,並剔除不穩定和位於邊緣上的點。
方向指定: 計算每個特徵點周圍的梯度方向,並將梯度方向最強的方向作為該特徵點的「主方向」,也是實現「旋轉不變性」的關鍵。
關鍵點描述: 將特徵點周圍的區域旋轉至「主方向」,然後將這個 16 × 16 的區域劃分成 4 × 4 的子區域。在每個子區域內,統計 8 個方向的梯度強度,最終形成一個 4 × 4 × 8 = 128 維的向量。這個128維的向量就是 SIFT 描述子。
雖然 SIFT 相當強大,但是計算成本也相對地高,這也衍生出了使用主成分分析 (Principal Component Analysis, PCA) 來降維、提升效率的 PCA-SIFT。這在保留大部分資訊的同時,大幅提高了匹配的效率。
加速穩健特徵 (Speeded Up Robust Features, SURF) 是從 SIFT 改良的方法,他使用了積分圖 (integral image) 來快速計算任意矩形區域的像素總和,並藉此把 SIFT 的高斯濾波改成更高效的盒式濾波。因此能比 SIFT 快上數倍的同時,維持他的準確與穩健性。
雖然 SIFT 跟 SURF 都相當有效,但他們都受到專利的保護。也因此開源社群跟 OpenCV 團隊推出了 ORB 演算法 (Oriented FAST and Rotated BRIEF) 做為替代方案,我們從名字就能知道它由兩個部分所組成。
特徵點偵測部份,ORB 選擇了速度極快的 FAST (Features from Accelerated Segment Test) 角點偵測演算法,原理是檢查一個像素點周圍圓圈上的像素,看是否存在連續的一段像素都比中心點亮或暗。
但原始的 FAST 不具備旋轉不變性。因此,ORB 在其基礎上增加了一步:計算角點附近的「強度質心 (intensity centroid)」,並將從角點指向質心的向量作為該特徵點的「主方向」。這一步被稱為 Oriented FAST。
BRIEF (Binary Robust Independent Elementary Features) 則是 ORB 的描述子部份,他的想法是:在特徵點周圍隨機選取 N 對像素點,然後進行 N 次比較
如果第一點的亮度 > 第二點的亮度,則結果為 1
反之結果為 0
這樣,N 次比較後就會得到一個 N-bit 的二進位字串,這就是 BRIEF 描述子。它的優點是生成速度快,儲存空間小,並且在匹配時可以使用漢明距離 (Hamming distance) 來進行比較。
但原始的 BRIEF 同樣不具備旋轉不變性。ORB 的貢獻在於,利用 Oriented FAST 計算出的主方向,將 BRIEF 選取點對的模式,也跟著「旋轉」到主方向上,這被稱為 rBRIEF。
import cv2
import numpy as np
img1_path = 'bird2-1.jpg'
img2_path = 'bird2-2.jpg'
img1 = cv2.imread(img1_path, cv2.IMREAD_GRAYSCALE) # 以灰階讀取
img2 = cv2.imread(img2_path, cv2.IMREAD_GRAYSCALE)
# --- 1. 初始化 ORB 偵測器 ---
# nfeatures: 希望偵測的特徵點數量上限
orb = cv2.ORB_create(nfeatures=1000)
# --- 2. 尋找關鍵點和描述子 ---
# 使用 detectAndCompute 一次性完成偵測和計算
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# --- 3. 建立 Brute-Force 匹配器 ---
# cv2.NORM_HAMMING: 用於 ORB/BRIEF 等二進位描述子的漢明距離計算
# crossCheck=True: 交叉檢查,過濾掉大量錯誤匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# --- 4. 進行特徵匹配 ---
matches = bf.match(des1, des2)
# --- 5. 對匹配結果進行排序 ---
# 根據匹配的「距離」進行排序,距離越小,匹配品質越高
matches = sorted(matches, key=lambda x: x.distance)
# --- 6. 繪製匹配結果 ---
# 我們只繪製前 50 個最佳匹配
result_image = cv2.drawMatches(img1, kp1, img2, kp2, matches[:50], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# 顯示結果
cv2.imshow("ORB Feature Matches", result_image)
cv2.waitKey(0)
cv2.imwrite("result.jpg", result_image)
cv2.destroyAllWindows()